//
//  LGSDocument.m
//  LogicSim
//
//  Created by Jeremy Pereira on 04/04/2012.
//  Copyright (c) 2012 Jeremy Pereira. All rights reserved.
//


#import "LGSDocument.h"
#import "LGSSelectNodeTypeSheetController.h"
#import "LGSConnectNodeSheetController.h"
#import "LGSNode.h"
#import "LGSEdge.h"
#import "LGSError.h" 
#import "LGSConnectionContext.h"
#import "LGSName.h"
#import "LGSNodeViewController.h"
#import "LGSModule.h"
#import "LGSInputMappingSheetController.h"


static NSString* const JSON_NODE_KEY 	= @"nodes";
static NSString* const JSON_NEXT_ID_KEY	= @"nextId";

@interface NSString(LGSDocumentExtensions)

-(NSString*) setterFromGetter;

@end

@interface LGSDocument ()

@property (readonly, nonatomic, strong) LGSNode* selectedNode;
@property (readonly, nonatomic, strong) LGSModule* moduleForSelectedNode;

@end

@implementation NSString(LGSDocumentExtensions)

-(NSString*) setterFromGetter
{
    NSString* firstLetter = [[self substringToIndex: 1] uppercaseString];
    NSString* rest = [self substringFromIndex: 1];
    return [NSString stringWithFormat: @"set%@%@:", firstLetter, rest];
}

@end

@implementation LGSDocument
{
    LGSModule* root;
    int nextId;
    NSWindow* selectNodeTypeSheet;
    NSOutlineView* componentView;
}

@synthesize componentView, selectNodeSheetController, connectNodeSheetController;

-(id) init
{
    self = [super init];
    if (self != nil)
    {
        root = [[LGSModule alloc] initWithNameString: @"Root"];
    }
    return self;
}

- (NSString *)windowNibName
{
    // Override returning the nib file name of the document
    // If you need to use a subclass of NSWindowController or if your document 
    // supports multiple NSWindowControllers, you should remove this method and 
    // override -makeWindowControllers instead.
    return @"LGSDocument";
}

- (void)windowControllerDidLoadNib:(NSWindowController *)aController
{
    [super windowControllerDidLoadNib:aController];
    // Add any code here that needs to be executed once the windowController has 
    // loaded the document's window.
}

+ (BOOL)autosavesInPlace
{
    return YES;
}

- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError
{
    NSLog(@"Type name to write %@", typeName);
    NSDictionary* rootJSON = [root jsonify];
    NSData* outputData = [NSJSONSerialization dataWithJSONObject: rootJSON
                                                         options: NSJSONWritingPrettyPrinted 
                                                           error: outError];
    return outputData;
}

- (BOOL)readFromData: (NSData*) data
              ofType: (NSString*) typeName
               error: (NSError**)outError
{
   	root = nil;
    id jsonObject = [NSJSONSerialization JSONObjectWithData: data 
                                                    options: 0 
                                                      error: outError];
    if (jsonObject != nil)
    {
        if ([jsonObject isKindOfClass: [NSDictionary class]])
        {
            LGSConnectionContext* context = [[LGSConnectionContext alloc] init];
            id lgsObject = [LGSNode makeNodeFromDictionary: jsonObject
                                         connectionContext: context
                                         		     error: outError];
			if (lgsObject != nil && ![lgsObject isKindOfClass: [LGSModule class]])
            {
                if (outError != nil)
                {
                    *outError = [LGSError deserialisationFailureReason: @"Top level object is not a module"];
                }
            }
            else
            {
                root = lgsObject;
            }
        }
    }
    return root != nil;
}

-(void) updateNode: (LGSNode*) updatedNode;
{
    [self updateChangeCount: NSChangeDone];
    [[self componentView] reloadData];    
}

-(IBAction) addNode: (id) sender
{
    [[self selectNodeSheetController] showWithDelegate: self];
}

-(IBAction) copyNode:(id)sender
{
    LGSNode* nodeToCopy = [self selectedNode];
    if (nodeToCopy != nil)
    {
        LGSModule* module = [nodeToCopy module];
    	if (module != nil)
        {
            [module addNode: [nodeToCopy copy]];
            [self updateNode: module];
        }
    }
}

-(IBAction) removeNode:(id)sender
{
    LGSNode* nodeToGo = [self selectedNode];
    if (nodeToGo != root)
    {
        LGSModule* theModule = [nodeToGo module];
        
        [theModule removeNode: nodeToGo];
        [self updateNode: theModule];
    }
}

-(LGSNode*) selectedNode
{
    LGSNode* ret = nil;
    NSInteger selectedIndex = [[self componentView] selectedRow];
    if (selectedIndex != -1)
    {
        ret = [[self componentView] itemAtRow: selectedIndex];
    }
    return ret;
}

-(LGSModule*) moduleForSelectedNode
{
    id ret = [self selectedNode];
    if (ret == nil)
    {
        ret = root;
    }
    else if (ret != nil && ![ret isKindOfClass: [LGSModule class]])
    {
        ret = [ret module];
    }
    return ret;
}

-(IBAction) connectNodes: (id) sender
{
    [[self connectNodeSheetController] showWithDelegate: self];
    
}

-(IBAction) makeInputMapping: (id) sender
{
    [[self inputMappingSheetController] showWithDelegate: self];
}


-(IBAction) selectedNodeChanged:(id)sender
{
    NSInteger rowIndex = [[self componentView] selectedRow];
    if (rowIndex != -1)
    {
        id item = [[self componentView] itemAtRow: rowIndex];
        if ([item isKindOfClass: [LGSNode class]])
        {
            [[self nodeViewController] setDisplayedNode: item];
        }
        else
        {
            [[self nodeViewController] setDisplayedNode: nil];
        }
    }
    else
    {
        [[self nodeViewController] setDisplayedNode: nil];
    }
}


#pragma mark -
#pragma mark LGSNodeTypeSelectorDelegate methods

-(NSWindow*) windowForNodeTypeSelector: (id) selector
{
    return [self windowForSheet];
}

-(void) nodeTypeSelector: (id) selector selectedIndex: (size_t)index
{
    NSString* className 
    	= [[[LGSNode nodeTypeDescriptors] objectAtIndex: index] 
           objectForKey: LGS_NODE_TYPE_KEY];
    id theClass = NSClassFromString(className);
    LGSName* newName = [LGSName nameWithString: @"untitled"];
    id newNode = [(LGSNode*)[theClass alloc] initWithName: newName];
    LGSModule* selectedNode = [self moduleForSelectedNode];
    [selectedNode addNode: newNode];
    
    [self updateNode: selectedNode];
}

-(void) nodeTypeSelectorCanceled: (id) selector
{
    NSLog(@"Node type selection canceled");
}

-(size_t) itemCountForNodeTypeSelector: (id) selector
{
    return [[LGSNode nodeTypeDescriptors] count];
}

-(NSString*) labelForNodeTypeSelector: (id) selector 
                              atIndex: (unsigned int) index
{
    return [[[LGSNode nodeTypeDescriptors] objectAtIndex: index] objectForKey: LGS_NODE_DESC_KEY];
    
}

#pragma mark -
#pragma mark LGSSheetDelegate methods

-(NSWindow*) windowForSheetController: (id) selector
{
    return [self windowForSheet];    
}

-(void) nodeSelector: (id) selector
 selectedSourceIndex: (size_t) sourceIndex
           destIndex: (size_t) destIndex
selectedSourceOutput: (NSString*) outputName
  selectedDestOutput: (NSString*) inputName

{
    LGSModule* theModule = [self moduleForSelectedNode];
    LGSName* sourceName = [[theModule nodeNames] objectAtIndex: sourceIndex];
    LGSName* destName = [[theModule nodeNames] objectAtIndex: destIndex];
    [[theModule nodeForName: sourceName] connect: [theModule nodeForName: destName]
                                      fromString: outputName
                                        toString: inputName];
    [self updateChangeCount: NSChangeDone];
    // Will force a reload of all data
    [[self nodeViewController] setDisplayedNode: [self selectedNode]];
}

-(void) nodeSelector: (id) selector
   selectedDestIndex: (size_t) destIndex
       selectedInput: (NSString*) inputNameString
   selectedDestInput: (NSString*) destInputNameString
{
    if ([[self selectedNode] isKindOfClass: [LGSModule class]])
    {
        LGSModule* theModule = (LGSModule*) [self selectedNode];
        LGSName* destNodeName = [self nameForNodeController: selector
                                                    atIndex: destIndex];
        [theModule addInputMappingFrom: [LGSName nameWithConnectionString: inputNameString]
                            toNodeName: [destNodeName nameWithConnectionString: destInputNameString]];
    }
    [self updateChangeCount: NSChangeDone];
    // Will force a reload of all data
    [[self nodeViewController] setDisplayedNode: [self selectedNode]];

}


-(size_t) itemCountForNodeSelectorController: (id) selector
{
    return [[self moduleForSelectedNode] subNodeCount];
}

-(NSArray*) inputNamesForNodeSelectorController: (id) selector
                                        atIndex: (size_t) index
{
    LGSModule* theModule = [self moduleForSelectedNode];
    LGSName* sourceName = [[theModule nodeNames] objectAtIndex: index];
    
    LGSNode* component = [theModule nodeForName: sourceName];
    return [component inputNames];
}

-(NSArray*) outputNamesForNodeSelectorController: (id) selector
                                         atIndex: (size_t) index
{
    LGSModule* theModule = [self moduleForSelectedNode];
    LGSName* sourceName = [[theModule nodeNames] objectAtIndex: index];
    
    LGSNode* component = [theModule nodeForName: sourceName];
    return [component outputNames];
}

-(LGSName*) nameForNodeController: (id) selector
                          atIndex: (size_t) index
{
	return [[[self moduleForSelectedNode] nodeNames] objectAtIndex: index];
}

-(NSArray*) moduleInputNamesForSelector:(id)selector
{
    return [[self selectedNode] inputNames];
}

-(void) sheetCanceled: (LGSSheetController*) sheetController
{
    
}


#pragma mark -
#pragma mark NSOutlineViewDataSource methods

-(id) outlineView: (NSOutlineView*) outlineView 
            child: (NSInteger) index 
           ofItem: (id) item
{
    id ret = nil;
    if (item == nil)
    {
        ret = root;
    }
    else
    {
        NSArray* nodeNames = [item nodeNames];
        LGSName* name = [nodeNames objectAtIndex: index];
        ret = [item nodeForName: name];
    }
    return ret;
}

-(BOOL) outlineView: (NSOutlineView*) outlineView isItemExpandable: (id) item
{
    return item == nil || [item subNodeCount] > 0;
}

-(NSInteger) outlineView: (NSOutlineView*) outlineView 
  numberOfChildrenOfItem: (id) item
{
    NSInteger ret = 0;
    if (item == nil)
    {
        ret = root == nil ? 0 : 1;
    }
    else
    {
        ret = [item subNodeCount];
    }
    return ret;
}

-(id)         outlineView: (NSOutlineView*) outlineView 
objectValueForTableColumn: (NSTableColumn*) tableColumn 
                   byItem: (id) item
{
    NSString* key = [tableColumn identifier];
    id ret;
    if ([item respondsToSelector: NSSelectorFromString(key)])
    {
        ret = [item valueForKey: key];
    }
    else 
    {
        ret = [NSString stringWithFormat: @"%@ not implemented", key];
        NSLog(@"%@", ret);
    }
    
    return ret;
}

-(void) outlineView: (NSOutlineView*) outlineView 
     setObjectValue: (id) object 
     forTableColumn: (NSTableColumn*) tableColumn 
             byItem: (id) item
{
    NSString* key = [tableColumn identifier];
    NSString* setter = [key setterFromGetter];
    if ([item respondsToSelector: NSSelectorFromString(setter)])
    {
        [item setValue: object forKey: key ];
        [self performSelectorOnMainThread: @selector(updateNode:)
                               withObject: item
                            waitUntilDone: NO];
    }
    else 
    {
        NSLog(@"%@ not implemented", setter);
    }
    
}

@end

#pragma mark -

@interface LGSNode(OutlineViewMethods)

@property (nonatomic, strong) NSString* inputsAsString;
@property (nonatomic, strong) NSString* outputsAsString;
@property (nonatomic, copy) NSString* nameAsString;

@end

@implementation LGSNode(OutlineViewMethods)

-(NSString*) nameAsString
{
    return [[self name] stringValue];
}

-(void) setNameAsString: (NSString*) nameAsString
{
    [self setName: [LGSName nameWithString: nameAsString]];
}

-(NSString*) inputsAsString
{
    NSMutableString* ret = [[NSMutableString alloc] init];
    for (NSString* inputName in [self inputNames])
    {
        if ([ret length] > 0)
        {
            [ret appendString: NSLocalizedString(@",", @"list separator")];
        }
        [ret appendFormat: @"%@", inputName];
    }
    return ret;
}

-(void) setInputsAsString: (NSString *)inputsAsString
{
    NSLog(@"Not implemented yet");
}

-(NSString*) outputsAsString
{
    NSMutableString* ret = [[NSMutableString alloc] init];
    for (NSString* outputName in [self outputNames])
    {
        if ([ret length] > 0)
        {
            [ret appendString: NSLocalizedString(@",", @"list separator")];
        }
        [ret appendFormat: @"%@", outputName];
    }
    return ret;
}

-(void) setOutputsAsString: (NSString *)inputsAsString
{
    NSLog(@"Not implemented yet");
}

@end